﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VirtualMarketProvider.cs" company="">
//   
// </copyright>
// <summary>
//   The virtual market provider.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Systemathics.FrameWork;
using Systemathics.FrameWork.Interfaces;

namespace Systemathics.Providers
{
    [ProviderName(StrategyMode.Virtual)]
    public sealed partial class VirtualMarketProvider : IOrderProvider
    {
        private readonly DataService DataService;
        private readonly Dictionary<Guid, LastFeed> LastFeeds;
        private readonly string PROVIDER_NAME ;
        private readonly List<Guid> ToRemove;
        private readonly Dictionary<Guid, OrderTracker> Trackers;
        private bool isConnected;
        private VirtualMarket VMParameters;

        private event EventHandler<FireMessageEventArgs>        _EmitError;
        private event EventHandler<FireMessageEventArgs>        _EmitConnectionStatus;
        private event EventHandler<OrderStatusEventArgs>        _EmitOrderStatus;
        private event EventHandler<OrderExectutionEventArgs>    _EmitOrderExecution;

        public void SetVirtualMarketParameters(VirtualMarketParameters parameters)
        {
            VMParameters = new VirtualMarket(parameters); 
        }

        #region constructor

        public VirtualMarketProvider()
        {
            var attribute = Attribute.GetCustomAttributes(GetType(), typeof(ProviderName))[0] as ProviderName;
            PROVIDER_NAME = attribute.Name;
        }
        public VirtualMarketProvider(DataService ds)
        {
            var attribute = Attribute.GetCustomAttributes(GetType(), typeof(ProviderName))[0] as ProviderName;
            PROVIDER_NAME = attribute.Name;
            isConnected = false;
            DataService = ds;
            ToRemove = new List<Guid>();
            LastFeeds = new Dictionary<Guid, LastFeed>();
            Trackers = new Dictionary<Guid, OrderTracker>();

            DataService.EmitConnectionStatus += DataProviderEmitConnectionStatus;
            DataService.EmitNewBlotter += DataProviderEmitNewBlotter;
            DataService.EmitNewTrade += DataProviderEmitNewTrade;
        }

        #endregion constructor

        #region IConnection Members

        string IConnection.Name
        {
            get { return PROVIDER_NAME; }
        }
        bool IConnection.isConnected
        {
            get { return isConnected; }
        }
        void IConnection.Connect()
        {
            if (DataService.isConnected)
            {
                isConnected = true;
                _EmitConnectionStatus(this, new FireMessageEventArgs(ConnectionStatus.Connected, Time.Now.TimeOfDay));
            }
        }
        void IConnection.Disconnect()
        {
            if (isConnected)
            {
                isConnected = false;
                _EmitConnectionStatus(this, new FireMessageEventArgs(ConnectionStatus.Disconnected, Time.Now.TimeOfDay));
            }
        }
        void IConnection.Suscribe(Instrument instrument)
        {
            lock (LastFeeds)
                LastFeeds[instrument.Id] = new LastFeed(instrument.Id);
        }
        void IConnection.Unsubscribe(Instrument instrument)
        {
            LastFeeds.Remove(instrument.Id);
            Trackers.Clear();
        }

        event EventHandler<FireMessageEventArgs> IConnection.EmitError
        {
            add { _EmitError += value; }
            remove { _EmitError -= value; }
        }
        event EventHandler<FireMessageEventArgs> IConnection.EmitConnectionStatus
        {
            add { _EmitConnectionStatus += value; }
            remove { _EmitConnectionStatus -= value; }
        }
        
        #endregion IConnection Members

        #region IOrderProvider Members

        bool IOrderProvider.Send(Order o)
        {                
            if (VMParameters.NotRejected())
            {
                if (o.Style == OrderStyle.Limit || o.Style == OrderStyle.Market)
                {
                    if (o.Ext.Tif == TimeInForce.MarketOnOpen || o.Ext.Tif == TimeInForce.GoodTillDate)
                        _EmitError(this, new FireMessageEventArgs(string.Format("{0}  order Time In Force Not currently Taken into account by Virtual Market", o.Ext.Tif), Time.Now.TimeOfDay));
                    else
                    {
                        lock (((ICollection)Trackers).SyncRoot)
                        {
                            Trackers[o.Id] = new OrderTracker(o);
                            Trackers[o.Id].SetSendTime(LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Time);
                            Trackers[o.Id].SetRemainingQty(o.TargetQuantity);
                            Trackers[o.Id].SetPendingQty(o.TargetQuantity);
                            Trackers[o.Id].SetisAcknoweldged(false);
                            Trackers[o.Id].SetisSent(true);
                            return true;
                        }
                    }
                }
                _EmitError(this, new FireMessageEventArgs(string.Format("{0}  order style Not Taken into account by Virtual Market", o.Style), Time.Now.TimeOfDay));
            }
            else // Rejecttime Seted even if not used becuse working in virtual instead of backtest
            {
                var osea = new OrderStatusEventArgs(o.Id, OrderStatus.REJECT, o.TargetQuantity, o.TargetPrice,o.RemainingQuantity, 0, "Rejection Probability Event Occured");
                osea.SendTime = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Time;
                osea.RejectTime = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Time;
                _EmitOrderStatus(this, osea);}
                return false;
        }
        bool IOrderProvider.Cancel(Order o)
        {
            lock (((ICollection) Trackers).SyncRoot)
            {
                if (Trackers.ContainsKey(o.Id))
                {
                    if (VMParameters.NotRejected())
                    {
                        Trackers[o.Id].SetCancelTime(LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Time);
                        Trackers[o.Id].SetisCancelling(true);
                        Trackers[o.Id].SetisCancelled(false);
                        return true;
                    }
                    //Trackers[o.Id].SetCancelRejectNumber();
                    _EmitOrderStatus(this, 
                                     new OrderStatusEventArgs(o.Id, OrderStatus.REJECT_CANCEL, o.TargetQuantity, 
                                                              o.TargetPrice, 0, o.RemainingQuantity, 
                                                              "Rejection Probability Event Occured")); // No "CancelRejectTime"
                }
                else
                    _EmitOrderStatus(this, new OrderStatusEventArgs(o.Id, OrderStatus.REJECT_CANCEL, o.TargetQuantity, o.TargetPrice, o.RemainingQuantity, 0,"Order doesn't belong to Virtual Market"));
            }

            return false;
        }
        bool IOrderProvider.Modify(Order o)
        {
            lock (((ICollection) Trackers).SyncRoot)
            {
                if (Trackers.ContainsKey(o.Id))
                {
                    if (VMParameters.NotRejected())
                    {
                        // Trackers[o.Id].SetModifyTime(Time.Now);
                        Trackers[o.Id].SetModifyTime(LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Time);
                        if (o.PendingQuantity >= Trackers[o.Id].TargetQuantity)
                            Trackers[o.Id].SetRemainingQty(o.PendingQuantity - Trackers[o.Id].TargetQuantity + Trackers[o.Id].RemainingQuantity);
                        else
                            Trackers[o.Id].SetRemainingQty(o.PendingQuantity);

                        Trackers[o.Id].TargetQuantity = o.PendingQuantity;
                        Trackers[o.Id].TargetPrice = o.PendingPrice;
                        Trackers[o.Id].SetPendingQty(o.PendingQuantity);
                        Trackers[o.Id].SetisModifying(true);
                        Trackers[o.Id].SetisModified(false);
                        return true;
                    }
                    _EmitOrderStatus(this, 
                                     new OrderStatusEventArgs(o.Id, OrderStatus.REJECT_MODIFY, o.PendingQuantity, 
                                                              o.PendingPrice, o.RemainingQuantity, 0, 
                                                              "Rejection Probability Event Occured")); // No "RejectModifyTime"
                }
                else
                    _EmitOrderStatus(this, 
                                     new OrderStatusEventArgs(o.Id, OrderStatus.REJECT_MODIFY, o.PendingQuantity,
                                                              o.PendingPrice, o.RemainingQuantity, 0, null)); // No "RejectModifyTime"
            }

            return false;
        }

        event EventHandler<OrderStatusEventArgs> IOrderProvider.EmitOrderStatus
        {
            add { _EmitOrderStatus += value; }
            remove { _EmitOrderStatus -= value; }
        }
        event EventHandler<OrderExectutionEventArgs> IOrderProvider.EmitOrderExecution
        {
            add { _EmitOrderExecution += value; }
            remove { _EmitOrderExecution -= value; }
        }

        #endregion

        #region DataProvider CallBack

        private void DataProviderEmitNewTrade(object sender, Trade e)
        {
            if (LastFeeds.ContainsKey(e.Id))
                LastFeeds[e.Id].Trade = e;
        }
        private void DataProviderEmitNewBlotter(object sender, Blotter e)
        {
            try
            {
                if (LastFeeds.ContainsKey(e.Id))
                {
                    LastFeeds[e.Id].Blotter = e;
                    LastFeeds[e.Id].CheckBlotter(QuoteLevel.One);
                    IEnumerable<OrderTracker> query;

                    lock (((ICollection)Trackers).SyncRoot)
                        query = Trackers.Values.Where(u => u.InstrumentId == e.Id && !u.isClosed).ToArray();

                    if (query.Any())
                    {
                        ToRemove.Clear();
                        foreach (OrderTracker ot in query)
                        {
                            if (ot.Style == OrderStyle.Market)
                            {
                                if (MarketOrderManager(ot))
                                    Trackers.Remove(ot.Id);
                            }
                            else if (ot.Style == OrderStyle.Limit)
                            {
                                var dictionary = ot;
                                if (LimitOrderManager(ref dictionary))
                                    Trackers.Remove(dictionary.Id);
                            }
                        }

                        //lock (((ICollection)Trackers).SyncRoot)
                        //{
                        //    foreach (Guid id in ToRemove)
                        //        Trackers.Remove(id);
                        //}
                    }
                }
            }
            catch (Exception rr)
            {
                
            }
           
        }

        private void DataProviderEmitConnectionStatus(object sender, FireMessageEventArgs e)
        {
            var cs = (ConnectionStatus) e.Message;
            switch (cs)
            {
                case ConnectionStatus.Connected:
                    isConnected = true;
                    _EmitConnectionStatus(this, new FireMessageEventArgs(ConnectionStatus.Connected, Time.Now.TimeOfDay));
                    break;
                case ConnectionStatus.Disconnected:
                    isConnected = false;
                    _EmitConnectionStatus(this, new FireMessageEventArgs(ConnectionStatus.Disconnected, Time.Now.TimeOfDay));
                    break;
            }
        }

        #endregion private methods

        #region private methods

        private Boolean CheckIfMarketExecution(ref OrderTracker o)
        {
            var tradedQty = 0;
            var tradedPrice = 0m;
            var hitQty = VMParameters.HitRatio();
            var exactQuantities = new[] { 0, 0, 0, 0, 0 };
            var ql = QuoteLevel.One;

            if (LastFeeds[o.InstrumentId].BlotterCheck)
            {
                switch (o.Side)
                {
                    case OrderSide.Buy:
                        if (o.RemainingQuantity <= LastFeeds[o.InstrumentId].Blotter.AskVolumeUntil(QuoteLevel.Five))
                        {
                            tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedAskPriceUntil(o.RemainingQuantity, ref exactQuantities, ref ql);
                            tradedQty = o.RemainingQuantity;

                            foreach (QuoteLevel level in Enum.GetValues(typeof(QuoteLevel)))
                                LastFeeds[o.InstrumentId].Blotter[level].AskSize -= (int)(hitQty * exactQuantities[(int)level]);
                        }
                        else
                        {
                            tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedAskPriceUntil(QuoteLevel.Five);
                            tradedQty = LastFeeds[o.InstrumentId].Blotter.AskVolumeUntil(QuoteLevel.Five);
                        }
                        break;
                    case OrderSide.Sell:
                        if (o.RemainingQuantity <= LastFeeds[o.InstrumentId].Blotter.BidVolumeUntil(QuoteLevel.Five))
                        {
                            tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedBidPriceUntil(o.RemainingQuantity, ref exactQuantities, ref ql);
                            tradedQty = o.RemainingQuantity;

                            foreach (QuoteLevel level in Enum.GetValues(typeof(QuoteLevel)))
                                LastFeeds[o.InstrumentId].Blotter[level].BidSize -= (int)(hitQty * exactQuantities[(int)level]);
                        }
                        else
                        {
                            tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedBidPriceUntil(QuoteLevel.Five);
                            tradedQty = LastFeeds[o.InstrumentId].Blotter.AskVolumeUntil(QuoteLevel.Five);
                        }
                        break;
                }
            }

            o.SetRemainingQty(Math.Max(0, o.RemainingQuantity - tradedQty));

            if (o.Ext.Tif == TimeInForce.FillOrKill)
            {
                if (tradedQty < o.TargetQuantity)
                    _EmitOrderStatus(this, new OrderStatusEventArgs(o.Id, OrderStatus.ACK_CANCEL, o.TargetQuantity, o.TargetPrice, 0, o.TargetQuantity, null));
                else 
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));

                if (o.RemainingQuantity == 0)
                    o.SetStatus(OrderStatus.FILLED);
                else
                    o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                return true;
            }
            if (o.Ext.Tif == TimeInForce.ImmediateOrCancel)
            {
                if (tradedQty > 0 && tradedPrice > 0)
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));

                if (tradedQty < o.TargetQuantity)
                    _EmitOrderStatus(this, new OrderStatusEventArgs(o.Id, OrderStatus.ACK_CANCEL, o.TargetQuantity, o.TargetPrice, 0, o.RemainingQuantity, null));

                if (o.RemainingQuantity == 0)
                    o.SetStatus(OrderStatus.FILLED);
                else
                    o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                return true;
            }
            if (o.Ext.Tif == TimeInForce.GoodTillCancel || o.Ext.Tif == TimeInForce.Day)
            {
                if (tradedQty > 0 && tradedPrice > 0)
                {
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));
                    if (o.RemainingQuantity == 0)
                        o.SetStatus(OrderStatus.FILLED);
                    else
                        o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                    return true;
                }
            }
            return false;
        }
        private Boolean CheckIfLimitExecution(ref OrderTracker o)
        {
            var tradedQty = 0;
            var tradedPrice = 0m;
            var hitQty = VMParameters.HitRatio(); 
            var hitorTake = VMParameters.HitorTake();
            var exactQuantities = new[] {0, 0, 0, 0, 0};
            var ql = QuoteLevel.One;
   
            if (LastFeeds[o.InstrumentId].BlotterCheck)
            {
                switch (o.Side)
                {
                    case OrderSide.Buy:
                        var price = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Ask;
                        if (o.RemainingQuantity <= LastFeeds[o.InstrumentId].Blotter.AskVolumeUntil(QuoteLevel.Five))
                        {
                            if (o.TargetPrice >= price)
                            {
                                tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedAskPriceUntil(o.RemainingQuantity, ref exactQuantities, ref ql);
                                tradedQty = o.RemainingQuantity;

                                foreach (QuoteLevel level in Enum.GetValues(typeof(QuoteLevel)))
                                    LastFeeds[o.InstrumentId].Blotter[level].AskSize -= (int)(hitQty * exactQuantities[(int)level]);
                            }
                            else if (hitorTake && o.TargetPrice >= LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Bid)
                            {
                                tradedPrice = o.TargetPrice;
                                var askSize = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].AskSize; //idem pour les autres
                                tradedQty = (askSize > o.RemainingQuantity) ? o.RemainingQuantity : askSize;
                                LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].AskSize -= tradedQty;
                            }
                        }
                        break;
                    case OrderSide.Sell:
                        var price_ = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Bid;
                        if (o.TargetPrice <= price_ && o.RemainingQuantity <= LastFeeds[o.InstrumentId].Blotter.BidVolumeUntil(QuoteLevel.Five))
                        {
                            tradedPrice = LastFeeds[o.InstrumentId].Blotter.WeigthedBidPriceUntil(o.RemainingQuantity, ref exactQuantities, ref ql);
                            tradedQty = o.RemainingQuantity;

                            foreach (QuoteLevel level in Enum.GetValues(typeof(QuoteLevel)))
                                LastFeeds[o.InstrumentId].Blotter[level].BidSize -= (int)(hitQty * exactQuantities[(int)level]);
                        }
                        else if (hitorTake && o.TargetPrice > price_ && o.TargetPrice <= LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].Ask && LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].BidSize >= o.RemainingQuantity)
                        {
                            tradedPrice = o.TargetPrice;
                            var bidSize = LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].BidSize; //idem pour les autres
                            tradedQty = (bidSize > o.RemainingQuantity) ? o.RemainingQuantity : bidSize;
                            LastFeeds[o.InstrumentId].Blotter[QuoteLevel.One].AskSize -= tradedQty;
                        }

                        break;
                    default:
                        break;
                }
            }
            if (tradedQty > 0.25 * o.TargetQuantity)
                tradedQty = (int)(hitQty * tradedQty);

            if (tradedQty == 0)
                tradedQty = 1;

            o.SetRemainingQty(Math.Max(0, o.RemainingQuantity - tradedQty));
            
            if (o.Ext.Tif == TimeInForce.FillOrKill)
            {
                if (tradedQty < o.TargetQuantity)
                    _EmitOrderStatus(this, new OrderStatusEventArgs(o.Id, OrderStatus.ACK_CANCEL, o.TargetQuantity, o.TargetPrice, 0, o.TargetQuantity, null));
                else
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));

                if (o.RemainingQuantity == 0)
                    o.SetStatus(OrderStatus.FILLED);
                else
                    o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                return true;
            }
            if (o.Ext.Tif == TimeInForce.ImmediateOrCancel)
            {
                if (tradedQty > 0 && tradedPrice > 0)
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));

                if (tradedQty < o.TargetQuantity)
                    _EmitOrderStatus(this, new OrderStatusEventArgs(o.Id, OrderStatus.ACK_CANCEL, o.TargetQuantity, o.TargetPrice, 0, o.RemainingQuantity, null));

                if (o.RemainingQuantity == 0)
                    o.SetStatus(OrderStatus.FILLED);
                else
                    o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                return true;
            }
            if (o.Ext.Tif == TimeInForce.GoodTillCancel || o.Ext.Tif == TimeInForce.Day)
            {
                if (tradedQty > 0 && tradedPrice > 0)
                {
                    _EmitOrderExecution(this, new OrderExectutionEventArgs(o.Id, tradedPrice, tradedQty, StrategyMode.Virtual.ToString()));
                    if (o.RemainingQuantity == 0)
                        o.SetStatus(OrderStatus.FILLED);
                    else
                        o.SetStatus(OrderStatus.PARTIALLY_FILLED);
                    return true;
                }
            }
           
            return false;
        }

        #endregion private methods

        void IDisposable.Dispose()
        {
            DataService.EmitConnectionStatus -= DataProviderEmitConnectionStatus;
            DataService.EmitNewBlotter -= DataProviderEmitNewBlotter;
            DataService.EmitNewTrade -= DataProviderEmitNewTrade;
        }
    }
}